unit Checkers.Visualizer;

interface

uses
  System.Types,
  Classes,
  Graphics,
  Dialogs,
  SysUtils,
  // GLscene
  VectorGeometry,
  VectorTypes,
  GLScene,
  GLObjects,
  GLWin32Viewer,
  GLVectorFileObjects,
  GLMaterial,
  GLTexture,
  GLGraphics,
  GLSGenerics,
  // PNG
  libPNG,
  // Project
  Checkers.Loader,
  Checkers.Cell,
  Checkers.Field,
  Checkers.Checker,
  Checkers.Camera,
  Checkers.Background,
  Checkers.MoveQuee,
  Checkers.Utils;

const
  FIELD_SIZE = 72;

type
  TBoardSize = (bs8x8, bs10x8, bs10x10, bs12x12);
  TDrawBoard = Array [0 .. FIELD_SIZE] of Integer;
  TCellsBoard = Array [0 .. FIELD_SIZE] of TCell;
  TCheckers = Array [0 .. FIELD_SIZE] of TChecker;
  THighlites = Array [0 .. FIELD_SIZE] of TColor;
  TCheckersList = GList<TChecker>;

  TCheckersVisualizer = class(TComponent)
  private
    FLoader: TCheckersLoader;
    FDefaultSize: TPoint;
    FScene: TGLScene;
    FViewer: TGLSceneViewer;
    FCamera: TCheckersCamera;
    FBackground: TBackground;
    FField: TField;
    FSelector: TGLPlane;
    FMoveSource: TGLPlane;
    FMoveTarget: TGLPlane;
    FBoard: TDrawBoard;
    FCells: TCellsBoard;
    FCheckers: TCheckers;
    FHighlites: THighlites;
    FCheckersBuffer: TCheckersList;
    FSize: TBoardSize;
    FMaterials: TGLMaterialLibrary;
    FIsFlipped: Boolean;
    FIsReversed: Boolean;
    FDoUpdateCamera: Boolean;
    FCameraPositions: Array [0 .. Integer(High(TBoardSize))] of TAffineVector;
    FSceneBuffer: TGLBitmap32;
    FScreenShot: TBitmap;
    FSelectedID: Integer;
    FStartID: Integer;
    FFinishID: Integer;
    FCheckersSpeed: Single;
    FResetRemovedCheckers: Boolean;
    FOnReverse: TNotifyEvent;
    FIsLocked: Boolean;
    FColorsReversed: Boolean;
    FCurrentQuee: TMoveQuee;
    FCurrentQueeID: Integer;
    FOnQueeComplete: TNotifyEvent;
    FOnQueeMoveComplete: TNotifyEvent;
    FOnQueeInterrupted: TNotifyEvent;
    procedure FillCheckersBuffer;
    procedure ClearBoard;
    function InternalMoveChecker(const AStart, AFinish: Integer): Boolean;
    procedure MoveNextInQuee(Sender: TObject);
    function AddDecal(AMaterialName: String): TGLPlane;
    procedure AddDecalToCell(const ACell: TCell; const ADecal: TGLPlane);
    procedure StoreCheckerID(const AChecker: TChecker; const AID: Integer);
    procedure UpdateCamera(const AInstant: Boolean = True);
    function GetBoard: TDrawBoard;
    procedure SetBoard(const ABoard: TDrawBoard);
    function GetCell(const AIndex: Integer): TCell;
    function GetKeepFOVMode: TGLCameraKeepFOVMode;
    procedure SetKeepFOVMode(const Value: TGLCameraKeepFOVMode);
    function GetBoardSize: TBoardSize;
    procedure SetBoardSize(const Value: TBoardSize);
    function GetChecker(const AIndex: Integer): TChecker;
    function GetIsFlipped: Boolean;
    procedure SetIsFlipped(const Value: Boolean);
    function GetIsReversed: Boolean;
    procedure SetIsReversed(const Value: Boolean);
    function GetShowText: Boolean;
    procedure SetShowText(const Value: Boolean);
    function GetHighliteColor(const AIndex: Integer): TColor;
    procedure SetHighliteColor(const AIndex: Integer; const Value: TColor);
    function GetCheckerSpeed: Single;
    procedure SetCheckerSpeed(const Value: Single);
    function GetOnReverse: TNotifyEvent;
    procedure SetOnReverse(const Value: TNotifyEvent);
    function GetOnQueeComplete: TNotifyEvent;
    function GetOnQueeInterrupted: TNotifyEvent;
    function GetOnQueeMoveComplete: TNotifyEvent;
    procedure SetOnQueeComplete(const Value: TNotifyEvent);
    procedure SetOnQueeInterrupted(const Value: TNotifyEvent);
    procedure SetOnQueeMoveComplete(const Value: TNotifyEvent);
    procedure ClearCheckers(const AElement: TQueeElement);
    procedure SingleMoveComplete(Sender: TObject);
    procedure UpgradeCheckerAt(AIndex: Integer);
    function GetColorsReveresd: Boolean;
    procedure SetColorsReversed(const Value: Boolean);
  public
    constructor Create(const AViewer: TGLSceneViewer; const AScene: TGLScene);
      reintroduce;
    function PickOnCoords(const AX, AY: Integer): TCell;
    function RaycastOnCoords(const AX, AY: Integer): TCell;
    function MoveChecker(const AStart, AFinish: Integer): Boolean;
    procedure RemoveChecker(const ACheckerID: Integer);
    procedure SelectCell(const ACell: TCell);
    procedure MarkMoveSource(const ACell: TCell);
    procedure MarkMoveTarget(const ACell: TCell);
    procedure MoveQuee(AQuee: TMoveQuee);
    procedure ClearSelect;
    procedure ClearMoveDecals;
    procedure ClearMoveSource;
    procedure ClearMoveTarget;
    procedure ClearHighlites;
    procedure NotifySizeChange(const ANewWidth, ANewHeight: Integer);
    procedure NotifyRevers;
    procedure SaveToFile(const AName: String; const AMaxSize: Integer = 0);
    destructor Destroy; override;
    property Board: TDrawBoard read GetBoard write SetBoard;
    property Cells[const AIndex: Integer]: TCell read GetCell;
    property Checkers[const AIndex: Integer]: TChecker read GetChecker;
    property Highlites[const AIndex: Integer]: TColor read GetHighliteColor
      write SetHighliteColor;
    property KeepFOVMode: TGLCameraKeepFOVMode read GetKeepFOVMode
      write SetKeepFOVMode;
    property Size: TBoardSize read GetBoardSize write SetBoardSize;
    property Field: TField read FField;
    property Camera: TCheckersCamera read FCamera;
    property IsFlipped: Boolean read GetIsFlipped write SetIsFlipped;
    property IsReversed: Boolean read GetIsReversed write SetIsReversed;
    property ShowText: Boolean read GetShowText write SetShowText;
    property CheckerSpeed: Single read GetCheckerSpeed write SetCheckerSpeed;
    property OnRevers: TNotifyEvent read GetOnReverse write SetOnReverse;
    property OnQueeComplete: TNotifyEvent read GetOnQueeComplete
      write SetOnQueeComplete;
    property OnQueeMoveComplete: TNotifyEvent read GetOnQueeMoveComplete
      write SetOnQueeMoveComplete;
    property OnQueeInterrupted: TNotifyEvent read GetOnQueeInterrupted
      write SetOnQueeInterrupted;
    property ReversedColors: Boolean read GetColorsReveresd
      write SetColorsReversed;
  end;

implementation

uses
  Checkers.InputManager,
  Checkers.Renderer;

{ TCheckersVisualizer }

constructor TCheckersVisualizer.Create(const AViewer: TGLSceneViewer;
  const AScene: TGLScene);
begin
  inherited Create(FScene);
  FDoUpdateCamera := True;
  FResetRemovedCheckers := True;
  CheckersInput.Visualizer := Self;
  FCheckersSpeed := 4.0;
  FDefaultSize := Point(800, 600);
  FCameraPositions[Integer(bs8x8)] := AffineVectorMake(2.7, 5.5, 0);
  FCameraPositions[Integer(bs10x8)] := AffineVectorMake(4, 6.2, 0);
  FCameraPositions[Integer(bs10x10)] := AffineVectorMake(3.5, 6.2, 0);
  FCameraPositions[Integer(bs12x12)] := AffineVectorMake(4.5, 7.2, 0);
  FIsReversed := False;
  FColorsReversed := False;
  FIsFlipped := False;
  FIsLocked := False;
  FViewer := AViewer;
  FScene := AScene;

  FCamera := TCheckersCamera.CreateAsChild(FScene.Objects);
  FCamera.FocalLength := 90;
  FCamera.DepthOfView := 1000;
  FCamera.NearPlaneBias := 0.1;
  FCamera.Visualizer := Self;
  FViewer.Camera := FCamera;
  FLoader := TCheckersLoader.Create(Self);
  FLoader.Load('Settings\visualizer.xml');

  FMaterials := TGLMaterialLibrary.Create(Self);
  FMaterials.Materials.DeleteUnusedMaterials;
  with FMaterials.AddTextureMaterial('Selector', 'Textures\selector.png') do
  begin
    Material.BlendingMode := bmTransparency;
    // Decals must not be affected by light
    Material.MaterialOptions := [moNoLighting];
    // Black color will be used as zero alpha
    // Material.Texture.ImageAlpha := tiaSuperBlackTransparent;
  end;
  with FMaterials.AddTextureMaterial('Highlite', 'Textures\highlite.png') do
  begin
    Material.BlendingMode := bmTransparency;
    Material.MaterialOptions := [moNoLighting];
  end;
  with FMaterials.AddTextureMaterial('MoveSource',
    'Textures\move_source.png') do
  begin
    Material.BlendingMode := bmTransparency;
    Material.MaterialOptions := [moNoLighting];
    Material.Texture.ImageAlpha := tiaSuperBlackTransparent;
  end;
  with FMaterials.AddTextureMaterial('MoveTarget',
    'Textures\move_target.png') do
  begin
    Material.BlendingMode := bmTransparency;
    Material.MaterialOptions := [moNoLighting];
    Material.Texture.ImageAlpha := tiaSuperBlackTransparent;
  end;
  with FMaterials.AddTextureMaterial('WhiteCell', 'Textures\Cells\white.png') do
  begin
    Material.MaterialOptions := [moNoLighting];
  end;
  with FMaterials.AddTextureMaterial('BlackCell', 'Textures\Cells\black.png') do
  begin
    Material.MaterialOptions := [moNoLighting];
  end;
  FBackground := TBackground.CreateAsChild(FScene.Objects);
  with FMaterials.AddTextureMaterial('BlackCell', 'Textures\background.png') do
  begin
    Material.MaterialOptions := [moNoLighting];
    FBackground.Material := Material;
  end;

  FField := TField.CreateAsChild(FScene.Objects);
  FField.Materials := FMaterials;
  FillCheckersBuffer;

  InitRenderer(FScene.Objects, FCamera);
  CheckersRenderer.MoveUp;

  CheckersRenderer.Checkers := FCheckersBuffer;
  CheckersRenderer.Field := FField;
  CheckersRenderer.Border := FField.BorderPlanes;

  FSelector := AddDecal('Selector');
  FMoveSource := AddDecal('MoveSource');
  FMoveTarget := AddDecal('MoveTarget');

  Size := bs8x8;
end;

procedure TCheckersVisualizer.FillCheckersBuffer;
var
  i: Integer;
  vChecker: TChecker;
begin
  FCheckersBuffer := TCheckersList.Create;
  for i := 0 to FIELD_SIZE do
  begin
    vChecker := TChecker.CreateAsChild(Field);
    vChecker.Speed := FCheckersSpeed;
    vChecker.Loader := FLoader;
    vChecker.Visible := False;
    FCheckersBuffer.Add(vChecker);
  end;
end;

procedure TCheckersVisualizer.NotifyRevers;
begin
  FIsReversed := not FIsReversed;
  FField.IsReversed := FIsReversed;
  if Assigned(FOnReverse) then
    FOnReverse(Self);
end;

procedure TCheckersVisualizer.NotifySizeChange(const ANewWidth,
  ANewHeight: Integer);
var
  vX, vY: Single;
begin
  FBackground.ViewerSizeChanged(ANewWidth, ANewHeight);
  vX := ANewWidth / FDefaultSize.X;
  vY := ANewHeight / FDefaultSize.Y;
  FCamera.SceneScale := MinFloat(vX, vY);
end;

function TCheckersVisualizer.MoveChecker(const AStart,
  AFinish: Integer): Boolean;
var
  vChecker: TChecker;
begin
  Result := False;
  if not FIsLocked then
  begin
    Result := InternalMoveChecker(AStart, AFinish);
    if Result and (FCheckers[AFinish] <> nil) then
    begin
      FIsLocked := True;
      vChecker := FCheckers[AFinish];
      vChecker.OnMoveComplete := SingleMoveComplete;
    end;
  end;
end;

procedure TCheckersVisualizer.SingleMoveComplete(Sender: TObject);
begin
  if Sender <> nil then
    TChecker(Sender).OnMoveComplete := nil;
  FIsLocked := False;
end;

function TCheckersVisualizer.InternalMoveChecker(const AStart,
  AFinish: Integer): Boolean;
var
  vStart: TCell;
  vFinish: TCell;
  vStartChecker: TChecker;
  vFinishChecker: TChecker;
begin
  Result := False;
  vStart := FCells[AStart];
  vFinish := FCells[AFinish];
  vStartChecker := FCheckers[AStart];
  vFinishChecker := FCheckers[AFinish];
  if (vStart <> nil) and (vFinish <> nil) then
  begin
    if vStartChecker <> nil then
    begin
      FCheckers[AStart] := vFinishChecker;
      FCheckers[AFinish] := vStartChecker;
      StoreCheckerID(vFinishChecker, AStart);
      StoreCheckerID(vStartChecker, AFinish);
      vStartChecker.MoveToCell(vFinish);
      Result := True;
    end;
  end;
end;

procedure TCheckersVisualizer.MoveQuee(AQuee: TMoveQuee);
begin
  if not FIsLocked then
  begin
    FCurrentQuee := AQuee;
    if FCurrentQuee.Count > 0 then
    begin
      FIsLocked := True;
      FCurrentQueeID := 0;
      MoveNextInQuee(nil);
    end;
  end;
end;

procedure TCheckersVisualizer.MoveNextInQuee(Sender: TObject);
var
  vElement: TQueeElement;
  vChecker: TChecker;
begin
  if (Sender <> nil) then
  begin
    if FCurrentQueeID <= 0 then
    begin
      FIsLocked := False;
      ClearCheckers(vElement);
      if Assigned(FOnQueeInterrupted) then
        FOnQueeInterrupted(vElement);
      Exit;
    end;
    vElement := FCurrentQuee.Items[FCurrentQueeID - 1];
    if Assigned(FOnQueeMoveComplete) then
      FOnQueeMoveComplete(vElement);
    if vElement.Upgrade then
      UpgradeCheckerAt(vElement.Finish);
  end;
  if FCurrentQueeID < FCurrentQuee.Count then
  begin
    vElement := FCurrentQuee.Items[FCurrentQueeID];
    if FCheckers[vElement.Finish] <> nil then
    begin
      FIsLocked := False;
      ClearCheckers(vElement);
      if Assigned(FOnQueeInterrupted) then
        FOnQueeInterrupted(vElement);
      Exit;
    end;
    if InternalMoveChecker(vElement.Start, vElement.Finish) then
    begin
      vChecker := FCheckers[vElement.Finish];
      ClearCheckers(vElement);
      vChecker.OnMoveComplete := MoveNextInQuee;
      FCurrentQueeID := FCurrentQueeID + 1;
    end
    else
    begin
      FIsLocked := False;
      ClearCheckers(vElement);
      if Assigned(FOnQueeInterrupted) then
        FOnQueeInterrupted(vElement);
      Exit;
    end;
    if Sender <> nil then
    begin
      if TChecker(Sender) <> vChecker then
        TChecker(Sender).OnMoveComplete := nil;
    end;
  end
  else
  begin
    if Sender <> nil then
      TChecker(Sender).OnMoveComplete := nil;
    FIsLocked := False;
    if Assigned(FOnQueeComplete) then
      FOnQueeComplete(FCurrentQuee);
  end;
end;

procedure TCheckersVisualizer.UpgradeCheckerAt(AIndex: Integer);
var
  vChecker: TChecker;
  vType: Integer;
begin
  vChecker := FCheckers[AIndex];
  vType := vChecker.ViewType;
  if vType = 1 then
    vChecker.ViewType := 2
  else if vType = -1 then
    vChecker.ViewType := -2;
end;

procedure TCheckersVisualizer.ClearCheckers(const AElement: TQueeElement);
var
  vChecker: TChecker;
begin
  vChecker := FCheckers[AElement.Finish];
  if vChecker <> nil then
    vChecker.OnMoveComplete := nil;
  vChecker := FCheckers[AElement.Start];
  if vChecker <> nil then
    vChecker.OnMoveComplete := nil;
end;

procedure TCheckersVisualizer.StoreCheckerID(const AChecker: TChecker;
  const AID: Integer);
begin
  if AChecker <> nil then
    FBoard[AID] := AChecker.ViewType
  else
    FBoard[AID] := 0;
end;

procedure TCheckersVisualizer.ClearBoard;
var
  i: Integer;
begin
  ClearHighlites;
  ClearSelect;
  ClearMoveDecals;
  for i := 0 to FCheckersBuffer.Count - 1 do
  begin
    FCheckersBuffer[i].MoveTo(FField);
    FCheckersBuffer[i].Reset(FResetRemovedCheckers);
    FCheckers[i] := nil;
  end;
end;

procedure TCheckersVisualizer.RemoveChecker(const ACheckerID: Integer);
begin
  if Assigned(FCheckers[ACheckerID]) then
    FCheckers[ACheckerID].RemoveFromBoard;
  FCheckers[ACheckerID] := nil;
  FBoard[ACheckerID] := 0;
end;

{$REGION 'Selection'}

function TCheckersVisualizer.AddDecal(AMaterialName: String): TGLPlane;
begin
  Result := TGLPlane.CreateAsChild(FField);
  Result.Direction.SetVector(0, 1, 0);
  Result.Material.MaterialLibrary := FMaterials;
  Result.Material.LibMaterialName := AMaterialName;
  Result.Width := 0.5;
  Result.Height := 0.5;
  Result.Position.Y := -0.39;
  Result.Pickable := False;
  Result.Visible := False;
end;

// Select field's cell over coords, using precise election.
function TCheckersVisualizer.PickOnCoords(const AX, AY: Integer): TCell;
var
  vResult: TGLBaseSceneOBject;
begin
  if (not FViewer.Buffer.RenderingContext.IsPraparationNeed) then
    vResult := FViewer.Buffer.GetPickedObject(AX, AY)
  else
    vResult := nil;
  if Assigned(vResult) and (vResult is TCell) then
  begin
    if not TCell(vResult).IsSelectebale then
      vResult := nil
  end
  else
    vResult := nil;
  Result := TCell(vResult);
end;

// Select field's cell over coords, not so precise as PickOnCoords, but faster.
function TCheckersVisualizer.RaycastOnCoords(const AX, AY: Integer): TCell;
var
  i: Integer;
  vCell: TCell;
  vResult: TGLBaseSceneOBject;
  vScreen, vStart, vDir: TVector;
begin
  vResult := nil;
  SetVector(vScreen, FViewer.Buffer.ScreenToWorld(AX, FViewer.Height - AY));
  SetVector(vDir, FViewer.Buffer.ScreenToVector(AX, FViewer.Height - AY));
  for i := 0 to FField.InteractiveCells.Count - 1 do
  begin
    vCell := FField.InteractiveCells[i];
    vStart := VectorSubtract(vScreen, VectorMake(vCell.AbsolutePosition[0],
      vCell.AbsolutePosition[1], vCell.AbsolutePosition[2]));
    if vCell.RaycastIntersect(vStart, vDir) then
    begin
      vResult := vCell;
      Break;
    end;
  end;
  Result := TCell(vResult);
end;

procedure TCheckersVisualizer.AddDecalToCell(const ACell: TCell;
  const ADecal: TGLPlane);
begin
  if (ADecal <> nil) and (ACell <> nil) then
  begin
    if ADecal.Position.DirectX <> ACell.Position.X then
      ADecal.Position.X := ACell.Position.X;
    if ADecal.Position.DirectZ <> ACell.Position.Z then
      ADecal.Position.Z := ACell.Position.Z;
    if not ADecal.Visible then
      ADecal.Visible := True;
  end;
end;

procedure TCheckersVisualizer.SelectCell(const ACell: TCell);
begin
  if Assigned(ACell) then
  begin
    FSelectedID := ACell.ID;
    AddDecalToCell(ACell, FSelector);
  end;
end;

procedure TCheckersVisualizer.MarkMoveSource(const ACell: TCell);
begin
  if Assigned(ACell) then
  begin
    FStartID := ACell.ID;
    AddDecalToCell(ACell, FMoveSource);
  end;
end;

procedure TCheckersVisualizer.MarkMoveTarget(const ACell: TCell);
begin
  if Assigned(ACell) then
  begin
    FFinishID := ACell.ID;
    AddDecalToCell(ACell, FMoveTarget);
  end;
end;

procedure TCheckersVisualizer.ClearSelect();
begin
  FSelector.Visible := False;
end;

procedure TCheckersVisualizer.ClearMoveDecals();
begin
  FMoveSource.Visible := False;
  FMoveTarget.Visible := False;
end;

procedure TCheckersVisualizer.ClearMoveSource;
begin
  FMoveSource.Visible := False;
end;

procedure TCheckersVisualizer.ClearMoveTarget;
begin
  FMoveTarget.Visible := False;
end;

procedure TCheckersVisualizer.ClearHighlites;
var
  i: Integer;
begin
  for i := 0 to FIELD_SIZE - 1 do
  begin
    if Assigned(FCells[i]) then
      FCells[i].ClearHighlite;
    FHighlites[i] := clBlack;
  end;
end;

function TCheckersVisualizer.GetHighliteColor(const AIndex: Integer): TColor;
begin
  if AIndex < FIELD_SIZE then
    Result := FHighlites[AIndex]
  else
    Result := clNone;
end;

procedure TCheckersVisualizer.SetHighliteColor(const AIndex: Integer;
  const Value: TColor);
begin
  if AIndex < FIELD_SIZE then
  begin
    if FCells[AIndex] <> nil then
    begin
      if Value = clBlack then
        FCells[AIndex].ClearHighlite
      else
        FCells[AIndex].Highlite(Value);
    end;
    FHighlites[AIndex] := Value;
  end;
end;

{$ENDREGION 'Selection'}

function TCheckersVisualizer.GetBoard: TDrawBoard;
begin
  Result := FBoard;
end;

procedure TCheckersVisualizer.SetBoard(const ABoard: TDrawBoard);
var
  i: Integer;
begin
  ClearBoard;
  FBoard := ABoard;
  FIsLocked := False;
  if FCurrentQuee <> nil then
    FCurrentQueeID := FCurrentQuee.Count;
  for i := 0 to Length(FBoard) - 1 do
  begin
    if (FCells[i] <> nil) and (FBoard[i] <> 0) then
    begin
      FCheckers[i] := FCheckersBuffer[i];
      FCheckers[i].StopMovement();
      FCheckers[i].OnMoveComplete := nil;
      if FColorsReversed then
        FCheckers[i].ViewType := -FBoard[i]
      else
        FCheckers[i].ViewType := FBoard[i];
      FCheckers[i].AbsolutePosition := FCells[i].AbsolutePosition;
    end;
  end;
end;

function TCheckersVisualizer.GetBoardSize: TBoardSize;
begin
  Result := FSize;
end;

procedure TCheckersVisualizer.SetBoardSize(const Value: TBoardSize);
var
  i: Integer;
  vHighlites: Array [0 .. FIELD_SIZE] of TColor;
begin
  FSize := Value;
  FResetRemovedCheckers := False;
  // Temporary highlites storage
  for i := 0 to FIELD_SIZE do
    vHighlites[i] := FHighlites[i];
  ClearHighlites;
  // Any size can be added into TBoardSize enum
  case Value of
    bs8x8:
      FField.Size := Point(8, 8);
    bs10x8:
      FField.Size := Point(10, 8);
    bs10x10:
      FField.Size := Point(10, 10);
    bs12x12:
      FField.Size := Point(12, 12);
  end;
  if FDoUpdateCamera then
    UpdateCamera;
  ClearBoard;
  // Deleting pointers
  for i := 0 to Length(FCells) - 1 do
    FCells[i] := nil;
  // Copying cells pointers from field
  for i := 0 to FField.InteractiveCells.Count - 1 do
    FCells[i] := FField.InteractiveCells[i];
  // Reset checkers positions
  Board := FBoard;
  // Marks and highlites restore
  MarkMoveSource(FCells[FStartID]);
  MarkMoveTarget(FCells[FFinishID]);
  for i := 0 to FIELD_SIZE do
    Highlites[i] := vHighlites[i];
  FResetRemovedCheckers := True;
end;

function TCheckersVisualizer.GetCell(const AIndex: Integer): TCell;
begin
  if (AIndex < Length(FCells)) then
    Result := FCells[AIndex]
  else
    Result := nil;
end;

function TCheckersVisualizer.GetChecker(const AIndex: Integer): TChecker;
begin
  Result := FCheckers[AIndex];
end;

function TCheckersVisualizer.GetCheckerSpeed: Single;
begin
  Result := FCheckersSpeed;
end;

procedure TCheckersVisualizer.SetCheckerSpeed(const Value: Single);
var
  i: Integer;
begin
  FCheckersSpeed := Value;
  for i := 0 to FCheckersBuffer.Count - 1 do
    FCheckersBuffer[i].Speed := FCheckersSpeed;
end;

function TCheckersVisualizer.GetColorsReveresd: Boolean;
begin
  Result := FColorsReversed;
end;

procedure TCheckersVisualizer.SetColorsReversed(const Value: Boolean);
var
  i:Integer;
begin
  if Value <> FColorsReversed then
  begin
    FColorsReversed := Value;
    for i := 0 to FCheckersBuffer.Count - 1 do
    begin
      if FCheckersBuffer[i] <> nil then
        FCheckersBuffer[i].ViewType := -FCheckersBuffer[i].ViewType;
    end;
    FScene.NotifyChange(Self);
  end;
end;

function TCheckersVisualizer.GetKeepFOVMode: TGLCameraKeepFOVMode;
begin
  Result := FViewer.Camera.KeepFOVMode;
end;

procedure TCheckersVisualizer.SetKeepFOVMode(const Value: TGLCameraKeepFOVMode);
begin
  FViewer.Camera.KeepFOVMode := Value;
end;

function TCheckersVisualizer.GetOnReverse: TNotifyEvent;
begin
  Result := FOnReverse;
end;

function TCheckersVisualizer.GetOnQueeComplete: TNotifyEvent;
begin
  Result := FOnQueeComplete;
end;

function TCheckersVisualizer.GetOnQueeInterrupted: TNotifyEvent;
begin
  Result := FOnQueeInterrupted;
end;

function TCheckersVisualizer.GetOnQueeMoveComplete: TNotifyEvent;
begin
  Result := FOnQueeMoveComplete;
end;

procedure TCheckersVisualizer.SetOnQueeComplete(const Value: TNotifyEvent);
begin
  FOnQueeComplete := Value;
end;

procedure TCheckersVisualizer.SetOnQueeInterrupted(const Value: TNotifyEvent);
begin
  FOnQueeInterrupted := Value;
end;

procedure TCheckersVisualizer.SetOnQueeMoveComplete(const Value: TNotifyEvent);
begin
  FOnQueeMoveComplete := Value;
end;

procedure TCheckersVisualizer.SetOnReverse(const Value: TNotifyEvent);
begin
  FOnReverse := Value;
end;

function TCheckersVisualizer.GetIsFlipped: Boolean;
begin
  Result := FIsFlipped;
end;

procedure TCheckersVisualizer.SetIsFlipped(const Value: Boolean);
begin
  FIsFlipped := Value;
  FField.IsFlipped := FIsFlipped;
  FDoUpdateCamera := False;
  Size := FSize;
  FDoUpdateCamera := True;
end;

function TCheckersVisualizer.GetIsReversed: Boolean;
begin
  Result := FIsReversed;
end;

procedure TCheckersVisualizer.SetIsReversed(const Value: Boolean);
begin
  FIsReversed := Value;
  UpdateCamera(False);
  FField.IsReversed := FIsReversed;
end;

function TCheckersVisualizer.GetShowText: Boolean;
begin
  Result := FField.ShowText;
end;

procedure TCheckersVisualizer.SetShowText(const Value: Boolean);
begin
  FField.ShowText := Value;
end;

procedure TCheckersVisualizer.UpdateCamera(const AInstant: Boolean = True);
var
  vCoords: TAffineVector;
  vX, vY, vZ: Single;
begin
  FCamera.TargetObject := nil;
  vCoords := FCameraPositions[Integer(FSize)];
  vX := vCoords[0];
  vY := vCoords[1];
  vZ := vCoords[2];
  if FIsReversed then
    vX := -vX;
  FCamera.CameraTarget := FField;
  FCamera.SetPosition(vX, vY, vZ, AInstant);
end;

procedure TCheckersVisualizer.SaveToFile(const AName: String;
  const AMaxSize: Integer = 0);
var
  vWidth: Integer;
  vHeight: Integer;
  vTempBitmap: TBitmap;
begin
  FSceneBuffer := FViewer.Buffer.CreateSnapShot;
  if AMaxSize > 0 then
  begin
    vWidth := FSceneBuffer.Width;
    vHeight := FSceneBuffer.Height;
    if vWidth > AMaxSize then
    begin
      vWidth := AMaxSize;
      vHeight := Floor(AMaxSize / FSceneBuffer.Width * FSceneBuffer.Height);
    end;
    if vHeight > AMaxSize then
    begin
      vHeight := AMaxSize;
      vWidth := Floor(AMaxSize / FSceneBuffer.Height * FSceneBuffer.Width);
    end;
    FScreenShot := TBitmap.Create;
    FScreenShot.Width := vWidth;
    FScreenShot.Height := vHeight;
    vTempBitmap := FSceneBuffer.Create32BitsBitmap;
    FScreenShot.Canvas.StretchDraw(Rect(0, 0, vWidth, vHeight), vTempBitmap);
    vTempBitmap.Free;
  end
  else
    FScreenShot := FSceneBuffer.Create32BitsBitmap;
  FScreenShot.SaveToFile(AName);
end;

destructor TCheckersVisualizer.Destroy;
begin
  FMaterials.Free;
  FLoader.Free;
  FCheckersBuffer.Free;
  if Assigned(FScreenShot) then
    FScreenShot.Free;
  if Assigned(FSceneBuffer) then
  begin
    FSceneBuffer.UnMipmap;
    FSceneBuffer.Free;
  end;
  inherited;
end;

end.
